﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Data.SqlServerCe;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OverStore.Configuration;
using OverStore.Exceptions;
using OverStore.Scoping;
using OverStore.Configuration.Components;
using OverStore.Runtime.Storage;
using OverStore.Configuration.Builder;
using OverStore.Sql.TableSchema;
using System.Reflection;
using OverStore.Configuration.Builder.Fluent;
using OverStore.Runtime.ObjectRepository.Mapping;
using OverStore.Runtime.ObjectRepository.CommandSet;

namespace OverStore.TutorialAsTest.Tutorial1.Lesson3
{
    /*********
     * In this lesson we consider loading of collection.
     * It will be very short lesson, because loading of collection
     * has object reader as background instance of objects.
     * 
     * Collection always requires parameters for loading. It is possible 
     * to use collection itself as parameters for loading.
     * 
     * Collection Repository (in part related to reading)
     * incapsulates filling collection. Collection may be any class,
     * and it is only required to configure how object will be added into 
     * collection and how collection will be prepared for loading.
     *********/
    [TestClass]
    public class Lesson3
    {
        /****
         * So, let consider simple collection loading example.
         * At first, we need test data. Our test data is part (first few columns)
         * of table Actor.
         *
         * ID                                       OverrideDbType    IsVirtual   Name                
         * ---------------------------------------------------------------------------------
         * 7ce284da-e012-4a3c-b1bd-27cf49166dab	    1	    True	    <Unknown Person>	
         * 57b57de2-5eb3-406d-9a72-3e85645a3f1e	    2	    True	    <Unknown terminal>	
         * f928af01-8e4a-4071-a2d6-7b461f2d34cd	    1	    False	    Sergey Tokarev	    
         *                  
         */

        static readonly Guid[] ActorIds =
            new[]
            {
                new Guid("7ce284da-e012-4a3c-b1bd-27cf49166dab"),
                new Guid("57b57de2-5eb3-406d-9a72-3e85645a3f1e"),
                new Guid("f928af01-8e4a-4071-a2d6-7b461f2d34cd")
            };

        /// <summary>
        /// Simple class contains part of information from Actor table.
        /// </summary>
        class Actor
        {
            public Guid Id { get; set; }

            public int Type { get; set; }

            public bool IsVirtual { get; set; }

            public string Name { get; set; }
        }

        #region Parameterless collection loading

        static IComponentConfiguration<IMapper<Actor>> CreateActorMapper()
        {
            return Component.Mapper.Delegate<Actor>(
                (item, name, p) =>
                {
                    if (name == "Id")
                        return item.Id;
                    if (name == "Type")
                        return item.Type;
                    if (name == "Name")
                        return item.Name;
                    if (name == "IsVirtual")
                        return item.IsVirtual;
                    if (name == "orig__Id")
                        return p.GetIdentifier<Actor, Guid>(item);
                    return Missing.Value;
                },
                (data, item, p) =>
                {
                    item.IsVirtual = data.GetBoolean("IsVirtual");
                    item.Type = data.GetInt32("Type");
                    item.Name = data.GetString("Name");
                });
        }

        static IComponentConfiguration<ICommandSet> CreateActorCommandSet()
        {
            return Component.CommandSet.FromTable(
                    Component.CreateTable("Actor")
                                      .Key<Guid>("Id")
                                      .Column<String>("Name")
                                      .Column<int>("Type")
                                      .Column<bool>("IsVirtual"), false);
        }

        /// <summary>
        /// Creates IOverStoreConfiguration allows to load generic List{ActorView} 
        /// with dummy (for example) parameter.
        /// </summary>
        static OverStoreConfiguration ConfigForListOfActorView(string connectionString)
        {
            return OverStoreFluent.Configure()
                .LogToConsole()
                .ConnectToDatabase<SqlCeConnection>(connectionString, ProcessOpenedReadersMode.ForciblyCloseReaders)
                .CustomClassRepository<Actor, Guid>()
                    .WithDepot(Component.Depot.WithInternalKey<Actor, Guid>("Id"))
                    .WithMapper(CreateActorMapper())
                    .WithCommands(CreateActorCommandSet())
                    .ByIdentifierAttaching(i => i.Id)
                    .ParameterlessReader(true)
                        .WithCommand(Component.AdoCommand.ReadCommand("select * from Actor"))
                        .WithIdentifierProvider(Component.IdentifierProvider.Delegate<Guid>(r => r.GetGuid("Id")))
                    .EndReader()
                .EndObjectRepository()
            .End();
        }

        /// <summary>
        /// Test is our configuration is valid and can
        /// get single instance by id.
        /// </summary>
        [TestMethod]
        public void CollectionRepositoryTestItemRepository()
        {
            var connectionString = TestHelper.CreateMyMoneyCopyAndBuildConnectionString();
            using (var session = ConfigForListOfActorView(connectionString).CreateSession())
            {
                var item = session.GetByIdentifier<Actor, Guid>(new Guid("7ce284da-e012-4a3c-b1bd-27cf49166dab"));
                Assert.AreEqual(new Guid("7ce284da-e012-4a3c-b1bd-27cf49166dab"), item.Id);
                Assert.AreEqual(1, item.Type);
                Assert.AreEqual(true, item.IsVirtual);
                Assert.AreEqual("<Unknown Person>", item.Name);
            }
        }

        /// <summary>
        /// Simplest example for loading list of actors.
        /// </summary>
        [TestMethod]
        public void ReloadCollectionSimple()
        {
            var list = new List<Actor>();
            var connectionString = TestHelper.CreateMyMoneyCopyAndBuildConnectionString();
            using (var session = ConfigForListOfActorView(connectionString).CreateSession())
            {
                session.ReloadCollection(list);
                CollectionAssert.AreEquivalent(ActorIds.ToList(), list.Select(a => a.Id).ToList());
                CollectionAssert.AreEquivalent(ActorIds.Select(id => session.GetByIdentifier<Actor, Guid>(id)).ToList(), list);
            }
        }

        static OverStoreConfiguration CreateConfigAuto(string connectionString)
        {
            return OverStoreFluent.Configure()
                .LogToConsole().ConnectToDatabase<SqlCeConnection>(connectionString, ProcessOpenedReadersMode.ForciblyCloseReaders)
                .CustomClassRepository<Actor, Guid>()
                    .WithDepot(Component.Depot.WithInternalKey<Actor, Guid>("Id"))
                    .WithMapper(CreateActorMapper())
                    .WithCommands(CreateActorCommandSet())
                    .ByIdentifierAttaching(i => i.Id)
                    .ParameterlessReader(true)
                        .WithCommand(Component.AdoCommand.ReadCommand("select * from Actor"))
                        .WithIdentifierProvider(Component.IdentifierProvider.Delegate<Guid>(r => r.GetGuid("Id")))
                    .EndReader()
                .EndObjectRepository()
            .End();
        }

        [TestMethod]
        public void ReloadCollectionAutoconfigTest()
        {
            var connectionString = TestHelper.CreateMyMoneyCopyAndBuildConnectionString();
            using (var session = CreateConfigAuto(connectionString).CreateSession())
            {
                var item = session.GetByIdentifier<Actor, Guid>(new Guid("7ce284da-e012-4a3c-b1bd-27cf49166dab"));
                Assert.AreEqual(new Guid("7ce284da-e012-4a3c-b1bd-27cf49166dab"), item.Id);
                Assert.AreEqual(1, item.Type);
                Assert.AreEqual(true, item.IsVirtual);
                Assert.AreEqual("<Unknown Person>", item.Name);
            }
        }

        [TestMethod]
        public void ReloadCollectionDifferentTypes()
        {
            var connectionString = TestHelper.CreateMyMoneyCopyAndBuildConnectionString();

            using (var session = CreateConfigAuto(connectionString).CreateSession())
            using (var scope = session.OpenScope())
            {
                var originalList = ActorIds.Select(i => session.GetByIdentifier<Actor, Guid>(i)).ToList();

                var list = new List<Actor>();
                session.ReloadCollection(list);
                CollectionAssert.AreEquivalent(originalList, list);

                var collection = new Collection<Actor>();
                session.ReloadCollection(collection);
                CollectionAssert.AreEquivalent(originalList, collection);

                var id = new Guid("7ce284da-e012-4a3c-b1bd-27cf49166dab");
                Assert.AreSame(list[1], collection[1]);
                scope.Complete();
            }
        }

        class MyList<T> : List<T>
        {
        }

        /// <summary>
        /// DefaultReadVariantConfiguration repository provider search repositories (for object, readers, collections) using generic parameters,
        /// not actual runtime type of argument.
        /// </summary>
        [TestMethod]
        public void ReloadCollectionCompatibleType()
        {
            var connectionString = TestHelper.CreateMyMoneyCopyAndBuildConnectionString();
            using (var session = ConfigForListOfActorView(connectionString).CreateSession())
            {
                var myList = new MyList<Actor>();
                session.ReloadCollection<Actor>(myList);

                CollectionAssert.AreEquivalent(
                    ActorIds.Select(id => session.GetByIdentifier<Actor, Guid>(id)).ToList(), myList);
            }
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ReloadCollectionNullArgument()
        {
            var connectionString = TestHelper.CreateMyMoneyCopyAndBuildConnectionString();
            using (var session = ConfigForListOfActorView(connectionString).CreateSession())
                session.ReloadCollection<Actor>(null);
        }

        #endregion

        #region ParameterizedReadVariantConfiguration Collection loading

        class ActorCollectionParameter
        {
            public string NameLike { get; set; }
        }

        /// <summary>
        /// Creates IOverStoreConfiguration allows to load generic List{ActorView} 
        /// with dummy (for example) parameter.
        /// </summary>
        static OverStoreConfiguration ConfigForListOfActorViewWithParameters(string connectionString)
        {
            return OverStoreFluent.Configure()
                .LogToConsole().ConnectToDatabase<SqlCeConnection>(connectionString, ProcessOpenedReadersMode.ForciblyCloseReaders)
                .CustomClassRepository<Actor, Guid>()
                    .WithDepot(Component.Depot.WithInternalKey<Actor, Guid>("Id"))
                    .WithMapper(CreateActorMapper())
                    .WithCommands(CreateActorCommandSet())
                    .ByIdentifierAttaching(i => i.Id)
                    .ParameterlessReader(true)
                        .WithCommand(Component.AdoCommand.ReadCommand("select * from Actor"))
                        .WithIdentifierProvider(Component.IdentifierProvider.Delegate<Guid>(r => r.GetGuid("Id")))
                    .EndReader()
                    .ParameterizedReader<ActorCollectionParameter>(true)
                        .ReadCommand()
                            .UseSqlCommand("select * from Actor where Name like @NameLike")
                            .InputParameter("NameLike", DbType.String)
                        .EndCommand()
                        .WithParametersMapper(Component.Mapper.Delegate<ActorCollectionParameter>(
                            (item, name, p) =>
                            {
                                if (name == "NameLike")
                                    return item.NameLike;
                                return Missing.Value;
                            },
                            (data, item, p) => { }))
                        .WithIdentifierProvider(Component.IdentifierProvider.Delegate<Guid>(r => r.GetGuid("Id")))
                    .EndReader()
                .EndObjectRepository()
            .End();
        }

        /// <summary>
        /// As usually test out confiugration by get single instance.
        /// </summary>
        [TestMethod]
        public void CollectionRepositoryWithParamsTestItem()
        {
            var connectionString = TestHelper.CreateMyMoneyCopyAndBuildConnectionString();
            using (var session = ConfigForListOfActorViewWithParameters(connectionString).CreateSession())
            {
                var item = session.GetByIdentifier<Actor, Guid>(new Guid("7ce284da-e012-4a3c-b1bd-27cf49166dab"));
                Assert.AreEqual(new Guid("7ce284da-e012-4a3c-b1bd-27cf49166dab"), item.Id);
                Assert.AreEqual(1, item.Type);
                Assert.AreEqual(true, item.IsVirtual);
                Assert.AreEqual("<Unknown Person>", item.Name);
            }
        }

        [TestMethod]
        public void ReloadCollectionWithParameters()
        {
            var connectionString = TestHelper.CreateMyMoneyCopyAndBuildConnectionString();
            using (var session = ConfigForListOfActorViewWithParameters(connectionString).CreateSession())
            {
                var list = new List<Actor>();
                var param = new ActorCollectionParameter { NameLike = "%Person%" };
                session.ReloadCollection(list, param);

                var id = new Guid("7ce284da-e012-4a3c-b1bd-27cf49166dab");
                CollectionAssert.AreEquivalent(new[] { id }.Select(i => session.GetByIdentifier<Actor, Guid>(i)).ToList(),
                    list);
            }
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ReloadCollectionNullCollection()
        {
            var connectionString = TestHelper.CreateMyMoneyCopyAndBuildConnectionString();
            using (var session = ConfigForListOfActorViewWithParameters(connectionString).CreateSession())
            {
                List<Actor> list = null;
                var param = new ActorCollectionParameter { NameLike = "%Person%" };
                session.ReloadCollection(list, param);
            }
        }


        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ReloadCollectionNullParameter()
        {
            var connectionString = TestHelper.CreateMyMoneyCopyAndBuildConnectionString();
            using (var session = ConfigForListOfActorViewWithParameters(connectionString).CreateSession())
            {
                List<Actor> list = new List<Actor>();
                ActorCollectionParameter param = null;
                session.ReloadCollection(list, param);
            }
        }

        [TestMethod]
        public void ReloadCollectionCompatibleCollectionType()
        {
            var connectionString = TestHelper.CreateMyMoneyCopyAndBuildConnectionString();
            using (var session = ConfigForListOfActorViewWithParameters(connectionString).CreateSession())
            {
                MyList<Actor> list = new MyList<Actor>();
                var param = new ActorCollectionParameter { NameLike = "%Person%" };
                session.ReloadCollection(list, param);
                Assert.AreEqual(1, list.Count);
            }
        }

        class MyParams : ActorCollectionParameter
        {
        }

        [TestMethod]
        [ExpectedException(typeof(ParameterTypeNotSupportedException))]
        public void ReloadCollectionWrongParametersType()
        {
            var connectionString = TestHelper.CreateMyMoneyCopyAndBuildConnectionString();
            using (var session = ConfigForListOfActorViewWithParameters(connectionString).CreateSession())
            {
                List<Actor> list = new List<Actor>();
                var param = new MyParams { NameLike = "%Person%" };
                session.ReloadCollection(list, param);
            }
        }


        [TestMethod]
        public void ReloadCollectionCompatibleParameters()
        {
            var connectionString = TestHelper.CreateMyMoneyCopyAndBuildConnectionString();
            using (var session = ConfigForListOfActorViewWithParameters(connectionString).CreateSession())
            {
                List<Actor> list = new List<Actor>();
                var param = new MyParams { NameLike = "%Person%" };
                session.ReloadCollection<Actor, ActorCollectionParameter>(list, param);
            }
        }

        #endregion
    }
}
